﻿#region Using Directives

using System;
using System.Collections.Generic;
using CCN.StyleCopPlugin.Highlights;
using JetBrains.ProjectModel;
using JetBrains.ReSharper.Daemon;
using JetBrains.ReSharper.Psi.Tree;
using JetBrains.Util;
using Microsoft.StyleCop;

#endregion

namespace CCN.StyleCopPlugin.FileProcessingInterfaces
{
    /// <summary>
    /// Class designed to process files using <c>StyleCop</c> 
    /// This was originally written against version 4.3
    /// </summary>
    public class StyleCopInterface : IFileProcessingInterface
    {
        /// <summary>
        /// The end of line character
        /// </summary>
        public const char LineEndCharacter = '\n';

        /// <summary>
        /// Holds XML with source analysis configuration
        /// </summary>
        //private string _sourceCodeAnalysisSettings;
        /// <summary>
        /// List of violations filled by source analysis engine
        /// </summary>
        private List<ViolationEventArgs> _violations;

        #region Class Methods

        /// <summary>
        /// Evaluates the specified file.
        /// </summary>
        /// <param name="file">The file.</param>
        /// <returns></returns>
        public List<HighlightingInfo> Evaluate(IFile file)
        {
            _violations = new List<ViolationEventArgs>();
            var highlights = new List<HighlightingInfo>();

            // TODO: handle *.g.cs && *.designer.cs

            var csharpSourceFile = file.ProjectFile;
            var tempFileManager = new TempFileManager();
            var projectPath = GetProjectPath(csharpSourceFile);

            // have some files to process - run analysis (will fill violations list)
            try
            {
                var tempFilePath = tempFileManager.CreateTempFile(file);
                ExecuteAnalysis(projectPath, tempFilePath);

                var violationHighlights = ConvertViolations(_violations, file);
                if(violationHighlights != null && violationHighlights.Count > 0)
                {
                    highlights.AddRange(violationHighlights);
                }
            }
            catch(Exception exception)
            {
                var processingHighlight = GetProcessingErrorHighlight(file, exception);
                highlights.Add(processingHighlight);
            }
            finally
            {
                tempFileManager.DeleteTempFile();
            }

            return highlights;
        }

        #region Private Helper Methods

        /// <summary>
        /// Executes the analysis.
        /// </summary>
        /// <param name="projectPath">The project path.</param>
        /// <param name="filePath">The file path.</param>
        private void ExecuteAnalysis(string projectPath, string filePath)
        {
            var console = new StyleCopConsole(null, false, null, new List<string>(), true);
            // TODO: fix constants later?)

            var configuration = new Configuration(new string[] {
                                                               });

            // initialize all projects to analyze
            var codeProjects = new List<CodeProject>();
            var codeProject = new CodeProject(projectPath.GetHashCode(), projectPath, configuration);
            console.Core.Environment.AddSourceCode(codeProject, filePath, null);
            codeProjects.Add(codeProject);

            // run source analysis (events will fill violations list)
            try
            {
                console.ViolationEncountered += OnViolationEncountered;
                console.Start(codeProjects.ToArray(), true);
            }
            finally
            {
                //if (IsTemporarySettingsFile(solutionPath, settingsFilePath))
                //    DeleteFile(settingsFilePath);
                console.ViolationEncountered -= OnViolationEncountered;
            }
        }


        /// <summary>
        /// Called when a violation is encountered.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="Microsoft.StyleCop.ViolationEventArgs"/> instance containing the event data.</param>
        private void OnViolationEncountered(object sender, ViolationEventArgs e)
        {
            _violations.Add(e);
        }

        #endregion Private Helper Methods

        #endregion Class Methods

        #region Static Helper Methods

        /// <summary>
        /// Gets a processing error highlight for the given exception.
        /// </summary>
        /// <param name="file">The file.</param>
        /// <param name="exception">The exception.</param>
        /// <returns></returns>
        private static HighlightingInfo GetProcessingErrorHighlight(IFile file, Exception exception)
        {
            var highlightRange = file.GetDocumentRange(new TextRange(0, 1));
            var error = new PluginProcessingError(exception, highlightRange);
            var highlight = new HighlightingInfo(error.Range, error);
            return highlight;
        }


        /// <summary>
        /// Converts the violations into highlights.
        /// </summary>
        /// <param name="violations">The violations.</param>
        /// <param name="file">The file.</param>
        /// <returns></returns>
        private static List<HighlightingInfo> ConvertViolations(List<ViolationEventArgs> violations, IFile file)
        {
            if(violations != null && violations.Count > 0)
            {
                var highlights = new List<HighlightingInfo>();

                //sort the violations so we can process them as we iterate through the file
                //This saves us iterating multiple times through the fileso should be much faster for large files
                violations.Sort((eventArgs, eventArgs1) => eventArgs.LineNumber.CompareTo(eventArgs1.LineNumber));

                //figure out the last line to be processed so we don't have to process the whole file
                var lastLineToProcess = violations[violations.Count - 1].LineNumber;

                //we need to keep track of which violaiton we are currently dealing with
                var currentViolationNumber = 0;

                //we need to keep track of the number of characters to our current location in the file
                var currentCharCount = 0;

                //split the file into lines
                var fileContents = file.GetText();

                var lines = GetLines(fileContents);

                //iterate through the lines
                for(var i = 0; i < lastLineToProcess + 1; i++)
                {
                    //if this line contains a violation
                    while((currentViolationNumber < violations.Count)
                          && i == (violations[currentViolationNumber].LineNumber - 1))
                    {
                        //convert the violation into a highlight for this line
                        var highlight = ConvertViolation(violations[currentViolationNumber], lines[i],
                                                         currentCharCount, file);
                        highlights.Add(highlight);

                        //move onto the next violation
                        currentViolationNumber++;
                    }

                    //add the number of characters on this line to our count. Add an extra one to cover the line end character.
                    currentCharCount = currentCharCount + lines[i].Length + 1;
                }

                return highlights;
            }

            return null;
        }

        /// <summary>
        /// Gets the files lines from the file contents string.
        /// </summary>
        /// <param name="fileContents">The file contents.</param>
        /// <returns></returns>
        private static List<string> GetLines(string fileContents)
        {
            var lineArray = fileContents.Split(LineEndCharacter);
            return new List<string>(lineArray);
        }

        /// <summary>
        /// Converts the violation into a highlight.
        /// </summary>
        /// <param name="violation">The <see cref="Microsoft.StyleCop.ViolationEventArgs"/> instance containing the event data.</param>
        /// <param name="lineText">The line text.</param>
        /// <param name="count">The count.</param>
        /// <param name="file">The file.</param>
        /// <returns></returns>
        private static HighlightingInfo ConvertViolation(ViolationEventArgs violation, string lineText, int count,
                                                         IFile file)
        {
            var length = lineText.Length;
            var startingWhitespaceLength = length - (lineText.TrimStart().Length);
            var validTextLength = lineText.Trim().Length;

            var highlightRange =
                file.GetDocumentRange(new TextRange(count + startingWhitespaceLength,
                                                    count + startingWhitespaceLength + validTextLength));

            var warning = new StyleCopViolation(violation, highlightRange);

            var highlight = new HighlightingInfo(warning.Range, warning);
            return highlight;
        }

        /// <summary>
        /// Gets the project path from the given source file.
        /// </summary>
        /// <param name="sourceFile">The source file.</param>
        /// <returns></returns>
        private static string GetProjectPath(IProjectElement sourceFile)
        {
            var project = sourceFile.GetProject();
            var projectProjectFile = project.ProjectFile;
            var path = GetPath(projectProjectFile);
            return path;
        }

        /// <summary>
        /// Gets the path for the given project file.
        /// </summary>
        /// <param name="projectFile">The project file.</param>
        /// <returns></returns>
        private static string GetPath(IProjectItem projectFile)
        {
            var filePath = projectFile.Location.FullPath;
            return filePath;
        }

        #endregion Static Helper Methods
    }
}